package com.doge.walknovel.business.novel

import android.util.LruCache
import androidx.lifecycle.MutableLiveData
import androidx.paging.DataSource
import com.doge.walknovel.api.*
import com.doge.walknovel.api.model.NovelDetailInfo
import com.doge.walknovel.api.model.CommentInfo
import com.doge.walknovel.business.novel.NovelDetailState.Companion.ID_COMMENTS
import com.doge.walknovel.business.novel.NovelDetailState.Companion.ID_COMMENTS_TOP
import com.doge.walknovel.business.novel.NovelDetailState.Companion.ID_INFO
import com.doge.walknovel.business.novel.NovelDetailState.Companion.ID_MIGHT_LIKE
import com.doge.walknovel.core.base.LoadStatus
import com.doge.walknovel.core.base.adapters.*
import com.doge.walknovel.utils.*
import kotlin.math.max

class NovelDetailRepository(
    private val novelId: Int,
    private val refer: String,
    private val referId: Int?,
) {
    companion object {
        val caches = LruCache<Int, NovelDetailInfo>(10)
    }

    val loading = MutableLiveData<LoadStatus>()
    val bookDetail = MutableLiveData<NovelDetailInfo>()
    val createLoading = MutableLiveData<LoadStatus?>()
    private var state: NovelDetailState = NovelDetailState()
    private var dataSource: XPagedKeyDataSource? = null

    fun requestNovelInfo() {
        require(isMainThread())
        if (bookDetail.value == null) {
            caches.get(novelId)?.let { runOnWork { reduceNovelInfo(it) } }
        }
        runOnIO {
            if (bookDetail.value == null && caches.get(novelId) == null) {
                val disk: String? = NovelDetailCacheHelper.loadCache(novelId)
                val cache = fromJson(disk, NovelDetailInfo::class.java)
                runOnUI { cache?.let { bookDetail.value = it } }
                runOnWork { cache?.let { reduceNovelInfo(cache) } }
            }
            api
                .novelDetail(novelId, refer, referId)
                .map(ApiResultMapTransformer())
                .observeOnWork()
                .subscribe(object : ObserverAdapter<NovelDetailInfo>() {
                    override fun onNext(t: NovelDetailInfo) {
                        runOnUI { bookDetail.value = t }
                        reduceNovelInfo(t)
                        caches.put(novelId, t)
                        NovelDetailCacheHelper.saveCache(novelId, t)
                    }

                    override fun onError(e: Throwable) {
                        e.printStackTrace()
                        reduceNovelError()
                    }
                })
        }
    }

    private fun reduceNovelInfo(data: NovelDetailInfo) {
        require(!isMainThread())
        val fetchOver = state.list.contains(ID_NO_MORE)
        val list = mutableListOf<String>()
        list.add(ID_INFO)
        if (data.likeList?.isNotEmpty() == true) {
            list.add(ID_MIGHT_LIKE)
        }
        list.add(ID_COMMENTS_TOP)
        list.addAll(state.commentsList.map { "$ID_COMMENTS${it}" })
        if (fetchOver) list.add(ID_NO_MORE) else list.add(ID_MORE_LOADING)
        state = state.copy(
            list = list,
            info = data.copy(likeList = state.info?.likeList ?: data.likeList)
        )
        dataSource?.invalidate()
        runOnUI { loading.value = LoadStatus.SUCCESS }
    }

    private fun reduceNovelError() {
        require(!isMainThread())
        if (state.info == null) state = state.copy(list = listOf(ID_ERROR))
        dataSource?.invalidate()
        runOnUI { loading.value = LoadStatus.FAILURE }
    }

    fun requestNovelComments(initial: Boolean) {
        require(isMainThread())
        val nextPage = if (initial) PAGE_INITIAL else (state.page + 1)
        api
            .novelCommentList(novelId, nextPage)
            .map(ApiResultMapTransformer())
            .subscribeOnIO()
            .observeOnWork()
            .subscribe(object : ObserverAdapter<List<CommentInfo>>() {
                override fun onNext(t: List<CommentInfo>) {
                    reduceOnNext(initial, t)
                }

                override fun onError(e: Throwable) {
                    reduceOnError()
                }
            })
    }

    private fun reduceOnNext(initial: Boolean, data: List<CommentInfo>) {
        val page = if (initial) PAGE_INITIAL else (state.page + 1)
        val comments = state.comments.appendData(data) { it.commentId }
        val preCommentsList = if (initial) listOf() else state.commentsList
        val commentsList = preCommentsList.addAll(data.map { it.commentId.toString() })
        val list = mutableListOf<String>()
        if (state.info != null) {
            list.add(ID_INFO)
            if (state.info?.likeList?.isNotEmpty() == true) {
                list.add(ID_MIGHT_LIKE)
            }
            list.add(ID_COMMENTS_TOP)
            list.addAll(commentsList.map { "$ID_COMMENTS${it}" })

            if (data.size < PAGE_SIZE) list.add(ID_NO_MORE) else list.add(ID_MORE_LOADING)
        }
        state = state.copy(
            page = page,
            info = state.info,
            list = list,
            commentsList = commentsList,
            comments = comments
        )
        dataSource?.invalidate()
    }

    private fun reduceOnError() {
        state = state.copy(
            list = state.list.remove(ID_MORE_LOADING).add(ID_NO_MORE)
        )
        dataSource?.invalidate()
    }

    fun requestSubmitNovelComments(content: String?, score: Int?) {
        require(isMainThread())
        if (content == null) return
        createLoading.value = LoadStatus.LOADING
        api.novelSubmitComment(
            novelId, content, score, 1
        )
            .map(ApiResultMapTransformer())
            .subscribeOnIO()
            .observeOnWork()
            .subscribe(object : ObserverAdapter<CommentInfo>() {
                override fun onNext(t: CommentInfo) {
                    reduceNewComments(t)
                    runOnUI {
                        createLoading.value = LoadStatus.SUCCESS
                        createLoading.value = null
                    }
                }

                override fun onError(e: Throwable) {
                    super.onError(e)
                    runOnUI {
                        createLoading.value = LoadStatus.FAILURE
                        createLoading.value = null
                    }
                }
            })
    }

    private fun reduceNewComments(data: CommentInfo) {
        state = state.copy(
            info = state.info?.copy(
                scoreFlag = 1,
                totalCommentCount = (state.info?.totalCommentCount ?: 0) + 1
            ),
            list = state.list.insertOrReplace(
                "$ID_COMMENTS${data.commentId}",
                state.list.indexOf(ID_COMMENTS_TOP) + 1
            ),
            commentsList = state.commentsList.insertOrReplace(data.commentId.toString(), 0),
            comments = state.comments.appendData(listOf(data)) { data.commentId }
        )
        dataSource?.invalidate()
    }

    fun requestSubmitReplyComments(commentId: Int, content: String?) {
        require(isMainThread())
        if (content == null) return
        api.novelSubmitComment(
            commentId, content, null, 3
        )
            .map(ApiResultMapTransformer())
            .subscribeOnIO()
            .observeOnWork()
            .subscribe(object : ObserverAdapter<CommentInfo>() {
                override fun onNext(t: CommentInfo) {
                    val data = state.comments[commentId] ?: return
                    reduceUpdateComment(
                        data.copy(
                            replyCount = max(
                                0,
                                (data.replyCount ?: 0) + 1
                            )
                        )
                    )
                }
            })
    }

    fun requestLikeComments(data: CommentInfo) {
        api
            .novelLikeComment(data.commentId)
            .compose(ApiResultComposeTransformer())
            .subscribeOnIO()
            .observeOnWork()
            .subscribe(object : ObserverAdapter<Any?>() {
                override fun onNext(t: Any?) {
                    reduceUpdateComment(
                        data.copy(
                            likeFlag = 1,
                            likeCount = max(0, (data.likeCount ?: 0) + 1)
                        )
                    )
                }
            })
    }

    fun requestUnLikeComments(data: CommentInfo) {
        api
            .novelUnLikeComment(data.commentId)
            .compose(ApiResultComposeTransformer())
            .subscribeOnIO()
            .observeOnWork()
            .subscribe(object : ObserverAdapter<Any?>() {
                override fun onNext(t: Any?) {
                    reduceUpdateComment(
                        data.copy(
                            likeFlag = 0,
                            likeCount = max(0, (data.likeCount ?: 0) - 1)
                        )
                    )
                }
            })
    }

    private fun reduceUpdateComment(data: CommentInfo) {
        state = state.copy(
            comments = state.comments.replace {
                var result: CommentInfo? = null
                if (data.commentId == it.commentId) result = data
                result
            }
        )
        dataSource?.invalidate()
    }

    fun reduceUpdateRecentChapterId(chapterId: Int)  {
        bookDetail.value = bookDetail.value?.copy(recentChapterId = chapterId)
    }

    fun dataSource(): DataSource.Factory<Int, Cell> =
        object : DataSource.Factory<Int, Cell>() {
            override fun create(): DataSource<Int, Cell> {
                return object : XPagedKeyDataSource() {
                    override fun totalCount(): Int {
                        return state.list.size
                    }

                    override fun loadRange(start: Int, count: Int): List<Cell?> {
                        return state.list.mapToCell(start, count) {
                            when {
                                it == ID_INFO -> Cell(ItemType.Info, it, extra = state.info)
                                it == ID_MIGHT_LIKE -> Cell(
                                    ItemType.MightLike,
                                    it,
                                    extra = state.info
                                )
                                it == ID_COMMENTS_TOP -> Cell(
                                    ItemType.CommentTop,
                                    it,
                                    extra = state.info
                                )
                                it.startsWith(ID_COMMENTS) -> Cell(
                                    ItemType.Comment,
                                    it.substring(ID_COMMENTS.length),
                                    extra = state.comments[it.substring(ID_COMMENTS.length)
                                        .toInt()]
                                )
                                else -> Cell(
                                    com.doge.walknovel.core.base.adapters.ItemType.Unknown,
                                    ""
                                )
                            }
                        }
                    }
                }.also { dataSource = it }
            }
        }



}